﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Noris.Tools.FrxEditor.Components
{
    /// <summary>
    /// Register : třída, která dovoluje aplikaci ukládat si cokoliv a později to zde opět najít, včetně persistence.
    /// </summary>
    public class Register
    {
        #region Singleton
        /// <summary>
        /// Konstruktor
        /// </summary>
        private Register()
        {
            this._Register = new Dictionary<string, Dictionary<string, object>>();
            this._Load();
            Noris.Schedule.Support.Steward.CallMeOnAppExit(this.OnAppExit);
        }
        /// <summary>
        /// Singleton Evaluatoru
        /// </summary>
        private static Register Current
        {
            get
            {
                if (_Current == null)
                {
                    lock (_CurrentLock)
                    {
                        if (_Current == null)
                        {
                            _Current = new Register();
                        }
                    }
                }
                return _Current;
            }
        }
        /// <summary>
        /// Zámek Singletonu
        /// </summary>
        private static object _CurrentLock = new object();
        /// <summary>
        /// Úložiště Singletonu
        /// </summary>
        private static Register _Current;
        /// <summary>
        /// Instanční úložiště pro zkompilované vzorce (text = vzorec)
        /// </summary>
        private Dictionary<string, Dictionary<string, object>> _Register;
        #endregion
        #region Public static metody: Set, Contain, GetValue, TryGetValue, RemoveName, RemoveFolder, GetFolders, GetValues
        /// <summary>
        /// Uloží danou hodnotu. Založí novou nebo přepíše stávající.
        /// </summary>
        /// <param name="folder">Složka registru. Case sensitive.</param>
        /// <param name="name">Název hodnoty. Case sensitive.</param>
        /// <param name="value">Hodnota. Do registru se ukládá přímo daný objekt.
        /// Persistuje se (do XML souboru) buď konvertorem (základní typy), 
        /// nebo rozhraním IXmlString pokud jej objekt má,
        /// anebo ToString().</param>
        public static void Set(string folder, string name, object value)
        {
            Current._Set(folder, name, value);
        }
        /// <summary>
        /// Zjistí, zda registr obsahuje danou hodnotu.
        /// </summary>
        /// <param name="folder">Složka registru. Case sensitive.</param>
        /// <param name="name">Název hodnoty. Case sensitive.</param>
        /// <returns></returns>
        public static bool Contain(string folder, string name)
        {
            return Current._Contain(folder, name);
        }
        /// <summary>
        /// Vrátí požadovanou hodnotu.
        /// Pokud složka nebo hodnota neexistuje, vyhodí chybu KeyNotFoundException.
        /// </summary>
        /// <param name="folder">Složka registru. Case sensitive.</param>
        /// <param name="name">Název hodnoty. Case sensitive.</param>
        /// <returns></returns>
        public static object GetValue(string folder, string name)
        {
            object value;
            if (Current._TryGetValue(folder, name, out value))
                return value;
            throw new KeyNotFoundException("Folder (" + folder + ") or Name (" + name + ") not exists in Register");
        }
        /// <summary>
        /// Zkusí najít požadovanou hodnotu.
        /// Vrací true = nalezena / false = nenalezena.
        /// Nalezená hodnota je jako objekt předána zpět v out parametru.
        /// </summary>
        /// <param name="folder">Složka registru. Case sensitive.</param>
        /// <param name="name">Název hodnoty. Case sensitive.</param>
        /// <param name="value">Out nalezená hodnota.</param>
        /// <returns></returns>
        public static bool TryGetValue(string folder, string name, out object value)
        {
            return Current._TryGetValue(folder, name, out value);
        }
        /// <summary>
        /// Zkusí najít požadovanou hodnotu.
        /// Vrací true = nalezena / false = nenalezena.
        /// Nalezenou hodnotu se pokusí přetypovat na požadovaný typ a uložit do out parametru.
        /// Pokud se to vše podaří, vrací true. Jinak vrací false.
        /// </summary>
        /// <typeparam name="T">Očekávaný (požadovaný) typ hledané hodnoty</typeparam>
        /// <param name="folder">Složka registru. Case sensitive.</param>
        /// <param name="name">Název hodnoty. Case sensitive.</param>
        /// <param name="value">Out nalezená hodnota.</param>
        /// <returns></returns>
        public static bool TryGetValue<T>(string folder, string name, out T value)
        {
            object data;
            if (Current._TryGetValue(folder, name, out data))
            {
                if (data is T)
                {
                    value = (T)data;
                    return true;
                }
            }
            value = default(T);
            return false;
        }
        /// <summary>
        /// Odstraní danou hodnotu.
        /// </summary>
        /// <param name="folder">Složka registru. Case sensitive.</param>
        /// <param name="name">Název hodnoty. Case sensitive.</param>
        public static void RemoveName(string folder, string name)
        {
            Current._RemoveName(folder, name);
        }
        /// <summary>
        /// Odstraní celou složku.
        /// </summary>
        /// <param name="folder">Složka registru. Case sensitive.</param>
        public static void RemoveFolder(string folder)
        {
            Current._RemoveFolder(folder);
        }
        /// <summary>
        /// Vrátí seznam existujících složek.
        /// </summary>
        /// <returns></returns>
        public static List<string> GetFolders()
        {
            return Current._Register.Keys.ToList();
        }
        /// <summary>
        /// Vrátí seznam existujících hodnot dané složky.
        /// Vrací soupis Key - Value.
        /// </summary>
        /// <param name="folder">Složka registru. Case sensitive.</param>
        /// <returns></returns>
        public static List<KeyValuePair<string, object>> GetValues(string folder)
        {
            Dictionary<string, object> folderDict = Current._GetFolder(folder, false);
            if (folderDict == null) return null;
            return folderDict.ToList();
        }
        /// <summary>
        /// Okamžitě uloží současná data do XML souboru.
        /// </summary>
        public static void Save()
        {
            Current._Save();
        }
        /// <summary>
        /// Plná cesta ke konfiguračnímu souboru. Nelze změnit.
        /// </summary>
        public static string XmlFile { get { return Current._PersistentFile; } }
        #endregion
        #region Private výkonné datové metody
        private void _Set(string folder, string name, object value)
        {
            this._Set(this._GetFolder(folder, true), name, value);
        }
        private void _Set(Dictionary<string, object> folderDict, string name, object value)
        {
            string key = _GetKey(name);
            if (folderDict.ContainsKey(key))
                folderDict[key] = value;
            else
                folderDict.Add(key, value);
        }
        private bool _Contain(string folder, string name)
        {
            Dictionary<string, object> folderDict = this._GetFolder(folder, false);
            if (folderDict == null) return false;
            string key = _GetKey(name);
            return folderDict.ContainsKey(key);
        }
        private bool _TryGetValue(string folder, string name, out object value)
        {
            value = null;
            Dictionary<string, object> folderDict = this._GetFolder(folder, false);
            if (folderDict == null) return false;
            string key = _GetKey(name);
            return folderDict.TryGetValue(key, out value);
        }
        private void _RemoveName(string folder, string name)
        {
            Dictionary<string, object> folderDict = this._GetFolder(folder, false);
            if (folderDict == null) return;
            string key = _GetKey(name);
            if (folderDict.ContainsKey(key))
                folderDict.Remove(key);
        }
        private void _RemoveFolder(string folder)
        {
            string key = _GetKey(folder);
            if (this._Register.ContainsKey(key))
                this._Register.Remove(key);
        }
        private Dictionary<string, object> _GetFolder(string folder, bool createEmpty)
        {
            string key = _GetKey(folder);
            Dictionary<string, object> folderDict;
            if (!this._Register.TryGetValue(key, out folderDict))
            {
                if (!createEmpty) return null;
                folderDict = new Dictionary<string, object>();
                this._Register.Add(key, folderDict);
            }
            return folderDict;
        }
        private static string _GetKey(string text)
        {
            return ((text == null) ? "" : text.Trim());
        }
        #endregion
        #region Persistence
        /// <summary>
        /// Načte data ze souboru do registru
        /// </summary>
        private void _Load()
        {
            this._Register.Clear();
            this._PersistentFile = System.IO.Path.Combine(Noris.Schedule.Support.Steward.ExecutablePath, "Register.xml");
            if (!System.IO.File.Exists(this._PersistentFile)) return;
            XDocument doc = XDocument.Load(this._PersistentFile);

            XElement root = doc.Element("register");
            if (root != null)
            {
                foreach (XElement xFolder in root.Elements())
                {
                    if (xFolder.Name.LocalName == "folder")
                    {
                        XAttribute xFolderName = xFolder.Attribute("name");

                        if (xFolderName != null)
                        {
                            Dictionary<string, object> folder = _GetFolder(xFolderName.Value, true);
                            foreach (XElement xValue in xFolder.Elements())
                            {
                                XAttribute xValueName = xValue.Attribute("name");
                                XAttribute xValueType = xValue.Attribute("type");
                                XAttribute xValueContent = xValue.Attribute("content");

                                if (xValueName != null && xValueType != null && xValueContent != null)
                                {
                                    this._Set(folder, xValueName.Value, Convertor.StringToObject(xValueType.Value, xValueContent.Value));
                                }
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Handler události Application.Exit: uloží data z registru do souboru
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void OnAppExit(object sender, EventArgs args)
        {
            this._Save();
        }
        /// <summary>
        /// Uložídata z registru do souboru
        /// </summary>
        private void _Save()
        {
            XDocument doc = new XDocument();
            XElement root = new XElement("register");
            doc.AddFirst(root);

            foreach (KeyValuePair<string, Dictionary<string, object>> folder in this._Register)
            {
                XElement xFolder = new XElement("folder");
                xFolder.Add(new XAttribute("name", folder.Key));
                foreach (KeyValuePair<string, object> value in folder.Value)
                {
                    XElement xValue = new XElement("value");
                    xValue.Add(new XAttribute("name", value.Key));
                    xValue.Add(new XAttribute("type", value.Value.GetType().NsName()));
                    if (value.Value != null)
                        xValue.Add(new XAttribute("content", Convertor.ObjectToString(value.Value)));
                    xFolder.Add(xValue);
                }
                root.Add(xFolder);
            }

            doc.Save(this._PersistentFile);
        }
        private string _PersistentFile;
        #endregion
    }
    #region class RegisterKey : klíč registru
    /// <summary>
    /// RegisterKey : klíč registru (Folder + Name)
    /// </summary>
    public class RegisterKey
    {
        #region Konstrukce, proměnné, overrides objektu
        /// <summary>
        /// Konstruktor s převzetím složky a názvu
        /// </summary>
        /// <param name="folder"></param>
        /// <param name="name"></param>
        public RegisterKey(string folder, string name)
        {
            this._Folder = (folder == null ? "" : folder.Trim());
            this._Name = (name == null ? "" : name.Trim());
            this._Hash = this._Folder.GetHashCode() ^ this._Name.GetHashCode() ^ 0x159C26AE;
        }
        private readonly string _Folder;
        private readonly string _Name;
        private readonly int _Hash;
        /// <summary>
        /// GetHashCode
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return this._Hash;
        }
        /// <summary>
        /// Equals
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            RegisterKey other = obj as RegisterKey;
            if (other == null) return false;
            return (this._Folder == other._Folder && this._Name == other._Name);
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this._Folder + "/" + this._Name;
        }
        #endregion
        #region Property
        /// <summary>
        /// Složka (adresář)
        /// </summary>
        public string Folder { get { return this._Folder; } }
        /// <summary>
        /// Název hodnoty
        /// </summary>
        public string Name { get { return this._Name; } }
        #endregion
        #region operator ==  a  !=
        /// <summary>
        /// Operátor ==
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator ==(RegisterKey a, RegisterKey b)
        {
            bool aNull = ((object)a) == null;
            bool bNull = ((object)b) == null;
            if (aNull && bNull) return true;
            if (aNull || bNull) return false;
            return (a._Hash == b._Hash && a._Folder == b._Folder && a._Name == b._Name);
        }
        /// <summary>
        /// Operátor !=
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool operator !=(RegisterKey a, RegisterKey b)
        {
            bool aNull = ((object)a) == null;
            bool bNull = ((object)b) == null;
            if (aNull && bNull) return false;
            if (aNull || bNull) return true;
            return !(a._Hash == b._Hash && a._Folder == b._Folder && a._Name == b._Name);
        }
        #endregion
    }
    #endregion
}
